﻿namespace DataCapture.Helper;

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using static DataCapture.Helper.PublicHelp;

public static class MapperCache
{
    
    public static Func<Func<string, int>, T> GetMapper<T>(ConcurrentDictionary<string, Delegate> mapperDelegates, PlcPart[] partList)
    {
        if (partList == null || partList.Length == 0)
        {
            return null;
        }
        var key = GenerateKey(partList);

        lock (mapperDelegates)
        {
            if (mapperDelegates.TryGetValue(key, out var del))
            {
                return (Func<Func<string, int>, T>)del;
            }

            del = BuildMapper<T>(partList);
            mapperDelegates[key] = del;
            return (Func<Func<string, int>, T>)del;
        }
    }

    private static string GenerateKey(PlcPart[] partList)
    {
        return string.Join(";", partList.Select(p => $"{p.PartType}:{p.PartAddress}"));
    }

    private static Delegate BuildMapper<T>(PlcPart[] partList)
    {
        var getDataParam = Expression.Parameter(typeof(Func<string, int>), "getData");
        var newExpr = Expression.New(typeof(T));
        var bindings = new List<MemberBinding>();

        foreach (var part in partList)
        {
            var propInfo = typeof(T).GetProperty(part.PartType);
            if (propInfo == null || propInfo.GetSetMethod() == null)
                continue;

            var addressConst = Expression.Constant(part.PartAddress, typeof(string));
            var rawValueExpr = Expression.Invoke(getDataParam, addressConst);

            Expression convertedExpr;
            if (propInfo.PropertyType == typeof(bool))
            {
                // int -> bool: value != 0
                convertedExpr = Expression.NotEqual(rawValueExpr, Expression.Constant(0));
            }
            else
            {
                convertedExpr = rawValueExpr;
            }

            var bind = Expression.Bind(propInfo, convertedExpr);
            bindings.Add(bind);
        }

        var memberInit = Expression.MemberInit(newExpr, bindings);
        var lambda = Expression.Lambda<Func<Func<string, int>, T>>(
            memberInit,
            getDataParam
        );

        return lambda.Compile();
    }
}

